#include "memorycachemanager.h"
#include <algorithm>
#include <QDebug>
namespace {
static inline QString joinKey(const QString& a, const QString& b, int c, int d) {
    return a + '|' + b + '|' + QString::number(c) + '|' + QString::number(d);
}
static inline QString joinKey(const QString& a, int b, int c) {
    return a + '|' + QString::number(b) + '|' + QString::number(c);
}
}
static MemoryCacheManager* g_instance = nullptr;
MemoryCacheManager::MemoryCacheManager(QObject* parent)
    : QObject(parent) {
    if (!g_instance) g_instance = this;
}
MemoryCacheManager* MemoryCacheManager::instance() {
    return g_instance;
}
void MemoryCacheManager::setCapacityMB(int totalMB, int l1MB, int l2MB) {
    QMutexLocker lock(&mutex_);
    if (l1MB + l2MB > totalMB) {
        const int half = std::max(1, totalMB / 2);
        l1MB = half;
        l2MB = totalMB - half;
    }
    l1CapacityBytes_ = static_cast<size_t>(l1MB) * 1024ull * 1024ull;
    l2CapacityBytes_ = static_cast<size_t>(l2MB) * 1024ull * 1024ull;
    maybeEvict();
}
std::optional<QImage> MemoryCacheManager::getOriginal(const QString& fileIdentity, int pageIndex, int dpiBucket) {
    QMutexLocker lock(&mutex_);
    const QString key = makeOriginalKey(fileIdentity, pageIndex, dpiBucket);
    auto it = l2Map_.find(key);
    if (it == l2Map_.end()) {
        ++l2Misses_;
        return std::nullopt;
    }
    touchEntry(key, l2Map_, l2Lru_);
    ++l2Hits_;
    return it->image;
}
void MemoryCacheManager::putOriginal(const QString& fileIdentity, int pageIndex, int dpiBucket, const QImage& image) {
    QMutexLocker lock(&mutex_);
    const QString key = makeOriginalKey(fileIdentity, pageIndex, dpiBucket);
    auto it = l2Map_.find(key);
    size_t bytes = imageBytes(image);
    if (it != l2Map_.end()) {
        l2UsedBytes_ -= it->bytes;
        it->image = image;
        it->bytes = bytes;
        it->lastUsedTick = ++tick_;
        l2Lru_.remove(key);
        l2Lru_.push_front(key);
    } else {
        CacheEntry entry;
        entry.image = image;
        entry.bytes = bytes;
        entry.lastUsedTick = ++tick_;
        entry.pinned = false;
        l2Lru_.push_front(key);
        l2Map_.insert(key, entry);
    }
    l2UsedBytes_ += bytes;
    maybeEvict();
}
std::optional<QImage> MemoryCacheManager::getComposite(const QString& fileIdentity, int mode, int compositeIndex, int dpiBucket) {
    QMutexLocker lock(&mutex_);
    const QString key = makeCompositeKey(fileIdentity, mode, compositeIndex, dpiBucket);
    auto it = l1Map_.find(key);
    if (it == l1Map_.end()) return std::nullopt;
    touchEntry(key, l1Map_, l1Lru_);
    return it->image;
}
void MemoryCacheManager::putComposite(const QString& fileIdentity, int mode, int compositeIndex, int dpiBucket, const QImage& image) {
    QMutexLocker lock(&mutex_);
    const QString key = makeCompositeKey(fileIdentity, mode, compositeIndex, dpiBucket);
    auto it = l1Map_.find(key);
    size_t bytes = imageBytes(image);
    if (it != l1Map_.end()) {
        l1UsedBytes_ -= it->bytes;
        it->image = image;
        it->bytes = bytes;
        it->lastUsedTick = ++tick_;
        l1Lru_.remove(key);
        l1Lru_.push_front(key);
    } else {
        CacheEntry entry;
        entry.image = image;
        entry.bytes = bytes;
        entry.lastUsedTick = ++tick_;
        entry.pinned = false;
        l1Lru_.push_front(key);
        l1Map_.insert(key, entry);
    }
    l1UsedBytes_ += bytes;
    maybeEvict();
}
void MemoryCacheManager::pinComposite(const QString& key) {
    QMutexLocker lock(&mutex_);
    auto it = l1Map_.find(key);
    if (it != l1Map_.end()) it->pinned = true;
}
void MemoryCacheManager::unpinComposite(const QString& key) {
    QMutexLocker lock(&mutex_);
    auto it = l1Map_.find(key);
    if (it != l1Map_.end()) it->pinned = false;
}
void MemoryCacheManager::pinOriginal(const QString& key) {
    QMutexLocker lock(&mutex_);
    auto it = l2Map_.find(key);
    if (it != l2Map_.end()) it->pinned = true;
}
void MemoryCacheManager::unpinOriginal(const QString& key) {
    QMutexLocker lock(&mutex_);
    auto it = l2Map_.find(key);
    if (it != l2Map_.end()) it->pinned = false;
}
void MemoryCacheManager::resetMetrics() {
    QMutexLocker lock(&mutex_);
    l2Hits_ = 0;
    l2Misses_ = 0;
}
quint64 MemoryCacheManager::l2Hits() const {
    QMutexLocker lock(&mutex_);
    return l2Hits_;
}
quint64 MemoryCacheManager::l2Misses() const {
    QMutexLocker lock(&mutex_);
    return l2Misses_;
}
size_t MemoryCacheManager::totalUsedBytes() const {
    QMutexLocker lock(&mutex_);
    return l1UsedBytes_ + l2UsedBytes_;
}
size_t MemoryCacheManager::totalCapacityBytes() const {
    QMutexLocker lock(&mutex_);
    return l1CapacityBytes_ + l2CapacityBytes_;
}
QString MemoryCacheManager::makeOriginalKey(const QString& fileIdentity, int pageIndex, int dpiBucket) const {
    return joinKey(fileIdentity, pageIndex, dpiBucket);
}
QString MemoryCacheManager::makeCompositeKey(const QString& fileIdentity, int mode, int compositeIndex, int dpiBucket) const {
    return joinKey(fileIdentity, QString::number(mode), compositeIndex, dpiBucket);
}
void MemoryCacheManager::touchEntry(const QString& key, QHash<QString, CacheEntry>& map, std::list<QString>& lru) {
    auto it = map.find(key);
    if (it == map.end()) return;
    it->lastUsedTick = ++tick_;
    // 从LRU列表中移除key（如果存在）
    lru.remove(key);
    // 插入到LRU列表的开头
    lru.push_front(key);
}
void MemoryCacheManager::maybeEvict() {
    // Evict L1 first, then L2, until both are under capacity
    if (l1UsedBytes_ > l1CapacityBytes_) {
        evictFrom(l1Map_, l1Lru_, l1UsedBytes_, l1CapacityBytes_);
    }
    if (l2UsedBytes_ > l2CapacityBytes_) {
        evictFrom(l2Map_, l2Lru_, l2UsedBytes_, l2CapacityBytes_);
    }
}
void MemoryCacheManager::evictFrom(QHash<QString, CacheEntry>& map, std::list<QString>& lru, size_t& usedBytes, size_t targetBytes) {
    int evictedCount = 0;
    while (usedBytes > targetBytes && !lru.empty()) {
        // Evict from the back (least recently used)
        const QString key = lru.back();
        lru.pop_back();
        auto mapIt = map.find(key);
        if (mapIt == map.end()) {
            // Key not found in map, continue to next
            continue;
        }
        if (mapIt->pinned) {
            // Skip pinned; move it slightly towards front to avoid hot looping
            lru.push_front(key);
            continue;
        }
        // Capture metadata before erasing from the map
        const size_t evictedBytes = mapIt->bytes;
        usedBytes -= evictedBytes;
        map.erase(mapIt);
        evictedCount++;
        emit evicted(key, &map == &l1Map_);
    }
    if (evictedCount > 0) {
    }
}
// === 补完文件清理机制：按文件清理内存缓存 ===
void MemoryCacheManager::clearFileCache(const QString& fileIdentity)
{
    QMutexLocker lock(&mutex_);
    // 清理L1合成页面缓存
    QStringList keysToRemove;
    for (auto it = l1Map_.begin(); it != l1Map_.end(); ++it) {
        if (it.key().startsWith(fileIdentity + "|")) {
            keysToRemove << it.key();
        }
    }
    for (const QString& key : keysToRemove) {
        auto it = l1Map_.find(key);
        if (it != l1Map_.end()) {
            l1UsedBytes_ -= it->bytes;
            l1Map_.erase(it);
            l1Lru_.remove(key);
            emit evicted(key, true); // true = composite
        }
    }
    // 清理L2原始页面缓存
    keysToRemove.clear();
    for (auto it = l2Map_.begin(); it != l2Map_.end(); ++it) {
        if (it.key().startsWith(fileIdentity + "|")) {
            keysToRemove << it.key();
        }
    }
    for (const QString& key : keysToRemove) {
        auto it = l2Map_.find(key);
        if (it != l2Map_.end()) {
            l2UsedBytes_ -= it->bytes;
            l2Map_.erase(it);
            l2Lru_.remove(key);
            emit evicted(key, false); // false = original
        }
    }
}
size_t MemoryCacheManager::imageBytes(const QImage& img) {
    const qsizetype sz = img.sizeInBytes();
    if (sz > 0) return static_cast<size_t>(sz);
    return static_cast<size_t>(img.width()) * static_cast<size_t>(img.height()) * 4ull;
}
